﻿#define PY_SSIZE_T_CLEAN
#include "Python.h"

#include "kconfig/interface/config.h"
#include "kconfig/interface/config_factory.h"
#include "src/kconfig/internal/bool_impl.h"
#include "src/kconfig/internal/domain.h"
#include "src/kconfig/internal/null_impl.h"
#include "src/kconfig/internal/number_impl.h"
#include "src/kconfig/internal/string_impl.h"
#include "util/os_util.hh"
#include "util/string_util.hh"

#include <exception>
#include <filesystem>
#include <iostream>
#include <memory>
#include <string>

#define DECLARE_CONF_FUNCTION(name)                                            \
  kconfig::AttributePtr name(const std::vector<kconfig::AttributePtr> &args);

#define IMPL_CONF_FUNCTION(name)                                               \
  kconfig::AttributePtr name(const std::vector<kconfig::AttributePtr> &args)

#define REG_CONF_FUNCTION(name)                                                \
  global_config_ptr->register_function(                                        \
      #name, std::bind(&name, std::placeholders::_1))

#define REG_CONF_ATTRIBUTE(name, value)                                        \
  global_config_ptr->get_root_domain()->addAttribute(#name, value);

IMPL_CONF_FUNCTION(os_name) {
#if defined(WIN32) || defined(_WIN32) || defined(_WIN64)
  return std::make_shared<StringImpl>("windows");
#else
  return std::make_shared<StringImpl>("linux");
#endif
}

IMPL_CONF_FUNCTION(print) {
  for (const auto &arg_ptr : args) {
    std::cout << arg_ptr->to_string();
  }
  std::cout << std::endl;
  return std::make_shared<NullImpl>();
}

IMPL_CONF_FUNCTION(is_debug) {
#if defined(DEBUG) || defined(_DEBUG)
  return std::make_shared<BoolImpl>(true);
#else
  return std::make_shared<BoolImpl>(false);
#endif
}

IMPL_CONF_FUNCTION(is_release) {
#if defined(DEBUG) || defined(_DEBUG)
  return std::make_shared<BoolImpl>(false);
#else
  return std::make_shared<BoolImpl>(true);
#endif
}

IMPL_CONF_FUNCTION(cwd) {
  return std::make_shared<StringImpl>(
      std::filesystem::current_path().string().c_str());
}

IMPL_CONF_FUNCTION(bin_path) {
  return std::make_shared<StringImpl>(kratos::util::get_binary_path());
}

IMPL_CONF_FUNCTION(bin_name) {
  return std::make_shared<StringImpl>(kratos::util::get_binary_name());
}

IMPL_CONF_FUNCTION(exec) {
  std::string cmd_line;
  for (const auto& arg : args) {
    if (!arg->isString()) {
      return std::make_shared<NullImpl>();
    }
    cmd_line += " " + arg->to_string();
  }
  return std::make_shared<StringImpl>(kratos::util::execute(cmd_line));
}

IMPL_CONF_FUNCTION(get_env) {
  if (args.empty() || args.size() != 1 || !args[0]->isString()) {
    return std::make_shared<NullImpl>();
  }
  return std::make_shared<StringImpl>(kratos::util::get_env(args[0]->to_string()));
}

std::unique_ptr<kconfig::Config> global_config_ptr;

auto get_object(kconfig::Attribute *attr_ptr) -> PyObject *;

static auto get_number(kconfig::Attribute *attr_ptr) -> PyObject * {
  auto num_str = attr_ptr->to_string();
  try {
    if (kratos::util::isinteger(num_str)) {
      return PyLong_FromLongLong(std::stoll(num_str));
    } else {
      return PyLong_FromDouble(std::stod(num_str));
    }
  } catch (std::exception &ex) {
    PyErr_SetString(PyExc_RuntimeError, ex.what());
  }
  Py_RETURN_NONE;
}

static auto get_string(kconfig::Attribute *attr_ptr) -> PyObject * {
  const auto &str = attr_ptr->string()->get();
  return PyUnicode_FromString(str.c_str());
}

static auto get_bool(kconfig::Attribute *attr_ptr) -> PyObject * {
  if (attr_ptr->boolean()->get()) {
    Py_RETURN_TRUE;
  } else {
    Py_RETURN_FALSE;
  }
}

static auto get_array(kconfig::Attribute *attr_ptr) -> PyObject * {
  auto *list_ptr = PyList_New(0);
  auto *attr_list = attr_ptr->array();
  for (auto i = 0; i < attr_list->getSize(); i++) {
    auto *attr_obj = get_object(attr_list->get(i));
    if (!attr_obj) {
      Py_XDECREF(list_ptr);
      Py_RETURN_NONE;
    }
    if (PyList_Append(list_ptr, attr_obj)) {
      PyErr_Clear();
      Py_XDECREF(list_ptr);
      Py_XDECREF(attr_obj);
      Py_RETURN_NONE;
    }
    if (!PyBool_Check(attr_obj) && (attr_obj != Py_None)) {
      Py_XDECREF(attr_obj);
    }
  }
  return list_ptr;
}

static auto get_table(kconfig::Attribute *attr_ptr) -> PyObject * {
  auto *dict_ptr = PyDict_New();
  auto *attr_dict = attr_ptr->table();
  while (attr_dict->hasNext()) {
    auto *pair_ptr = attr_dict->next();
    auto key_str = pair_ptr->key();
    auto *value_ptr = pair_ptr->value();
    auto *value_obj_ptr = get_object(value_ptr);
    if (!value_obj_ptr) {
      Py_XDECREF(dict_ptr);
      Py_RETURN_NONE;
    }
    auto *key_obj_ptr = PyUnicode_FromString(key_str.c_str());
    if (!key_obj_ptr) {
      Py_XDECREF(dict_ptr);
      Py_XDECREF(value_obj_ptr);
      Py_RETURN_NONE;
    }
    if (PyDict_SetItem(dict_ptr, key_obj_ptr, value_obj_ptr)) {
      PyErr_Clear();
      Py_XDECREF(dict_ptr);
      Py_XDECREF(key_obj_ptr);
      Py_XDECREF(value_obj_ptr);
      Py_RETURN_NONE;
    }
    if (!PyBool_Check(value_obj_ptr) && (value_obj_ptr != Py_None)) {
      Py_XDECREF(value_obj_ptr);
    }
    Py_XDECREF(key_obj_ptr);
  }
  return dict_ptr;
}

static auto get_domain(kconfig::Attribute *attr_ptr) -> PyObject * {
  auto *dict_ptr = PyDict_New();
  auto *attr_dict = attr_ptr->zone();
  while (attr_dict->hasNext()) {
    auto *zone_attr_ptr = attr_dict->next();
    auto name_str = zone_attr_ptr->name();
    auto *value_obj_ptr = get_object(zone_attr_ptr);
    if (!value_obj_ptr) {
      Py_XDECREF(dict_ptr);
      Py_RETURN_NONE;
    }
    auto *key_obj_ptr = PyUnicode_FromString(name_str.c_str());
    if (!key_obj_ptr) {
      Py_XDECREF(dict_ptr);
      Py_XDECREF(value_obj_ptr);
      Py_RETURN_NONE;
    }
    if (PyDict_SetItem(dict_ptr, key_obj_ptr, value_obj_ptr)) {
      PyErr_Clear();
      Py_XDECREF(dict_ptr);
      Py_XDECREF(key_obj_ptr);
      Py_XDECREF(value_obj_ptr);
      Py_RETURN_NONE;
    }
    if (!PyBool_Check(value_obj_ptr) && (value_obj_ptr != Py_None)) {
      Py_XDECREF(value_obj_ptr);
    }
    Py_XDECREF(key_obj_ptr);
  }
  return dict_ptr;
}

auto get_object(kconfig::Attribute *attr_ptr) -> PyObject * {
  auto type = attr_ptr->getType();
  switch (type) {
  case kconfig::Attribute::NUMBER:
    return get_number(attr_ptr);
    break;
  case kconfig::Attribute::STRING:
    return get_string(attr_ptr);
    break;
  case kconfig::Attribute::BOOL:
    return get_bool(attr_ptr);
    break;
  case kconfig::Attribute::ARRAY:
    return get_array(attr_ptr);
    break;
  case kconfig::Attribute::TABLE:
    return get_table(attr_ptr);
    break;
  case kconfig::Attribute::ZONE:
    return get_domain(attr_ptr);
    break;
  default:
    break;
  }
  Py_RETURN_NONE;
}

static auto OpenConfig(PyObject * /*self*/, PyObject *args) -> PyObject * {
  char *config_path{nullptr};
  if (!PyArg_ParseTuple(args, "s", &config_path)) {
    Py_RETURN_FALSE;
  }
  if (!config_path) {
    Py_RETURN_FALSE;
  }
  global_config_ptr.reset(ConfigFactory::createConfig());
  REG_CONF_FUNCTION(os_name);
  REG_CONF_FUNCTION(print);
  REG_CONF_FUNCTION(is_debug);
  REG_CONF_FUNCTION(is_release);
  REG_CONF_FUNCTION(cwd);
  REG_CONF_FUNCTION(bin_path);
  REG_CONF_FUNCTION(bin_name);
  REG_CONF_FUNCTION(exec);
  REG_CONF_FUNCTION(get_env);
  REG_CONF_ATTRIBUTE(K, std::make_shared<NumberImpl>("1024"));
  REG_CONF_ATTRIBUTE(M, std::make_shared<NumberImpl>("1048576"));
  REG_CONF_ATTRIBUTE(G, std::make_shared<NumberImpl>("1073741824"));
  try {
    global_config_ptr->load("", config_path);
  } catch (std::exception &ex) {
    PyErr_SetString(PyExc_RuntimeError, ex.what());
    return nullptr;
  }
  Py_RETURN_TRUE;
}

static auto GetConfig(PyObject * /*self*/, PyObject *args) -> PyObject * {
  if (!global_config_ptr) {
    Py_RETURN_NONE;
  }
  char *attr_name{nullptr};
  if (!PyArg_ParseTuple(args, "s", &attr_name)) {
    Py_RETURN_NONE;
  }
  if (!attr_name) {
    Py_RETURN_NONE;
  }
  auto *attr_ptr = global_config_ptr->get(attr_name);
  if (!attr_ptr) {
    Py_RETURN_NONE;
  }
  return get_object(attr_ptr);
}

static PyMethodDef buffer_methods[] = {
    {"OpenConfig", OpenConfig, METH_VARARGS, "Open config file"},
    {"GetConfig", GetConfig, METH_VARARGS, "Get config attribute"},
    {nullptr, nullptr, 0, nullptr} /* Sentinel */
};

static void rpc_module_cleanup() {}

#if !defined(_MSC_VER)
#if defined(DEBUG) || defined(_DEBUG)
#define INIT_FUNC PyMODINIT_FUNC PyInit_config_d(void)
#define MOD_NAME "config_d"
#else
#define INIT_FUNC PyMODINIT_FUNC PyInit_config(void)
#define MOD_NAME "config"
#endif // defined(DEBUG) || defined(_DEBUG)
#else
#define INIT_FUNC PyMODINIT_FUNC PyInit_config(void)
#define MOD_NAME "config"
#endif // !defined(_MSC_VER)

#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3

static struct PyModuleDef config_module = {
    PyModuleDef_HEAD_INIT, MOD_NAME, "Config framework", -1, buffer_methods};

INIT_FUNC {
  auto *m = PyModule_Create(&config_module);
  if (!m) {
    return nullptr;
  }
  if (Py_AtExit(rpc_module_cleanup) == -1) {
    return nullptr;
  }
  return m;
}

#else

INIT_FUNC {
  if (Py_InitModule(MOD_NAME, buffer_methods)) {
    Py_AtExit(rpc_module_cleanup);
  }
}

#endif // PY_MAJOR_VERSION >= 3
